﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NFramework.ObjectModel
{
    public class LazyInstance<TIdentifier, TObject>
        where TIdentifier : IComparable<TIdentifier>
        where TObject : IIdentifiable<TIdentifier>
    {
        public LazyInstance(Func<TIdentifier, TObject> instanceGetter)
        {
            if (instanceGetter == null) {
                throw new ArgumentNullException("instanceGetter");
            }
            _instanceGetter = instanceGetter;
            NullIfNegative = true;
            FailOnLoadException = true;
        }
        private Func<TIdentifier, TObject> _instanceGetter;

        public bool FailOnLoadException { get; set; }
        public bool NullIfNegative { get; set; }

        private TObject GetInstance()
        {
            try
            {
                return _instanceGetter.Invoke(this.Identifier);
            }
            catch
            {
                if (FailOnLoadException)
                {
                    throw;
                }
                else
                {
                    this.Identifier = default(TIdentifier);
                    return default(TObject);
                }
            }
        }

        public bool IsInstantiated
        {
            get
            {
                return (_instance != null);
            }
        }

        public TObject Instance
        {
            get
            {
                if (_instance == null || !_instance.Id.Equals(Identifier))
                {
                    if (_identifier.HasValueSet && Identifier != null && Identifier.CompareTo(default(TIdentifier)) > 0 || !NullIfNegative)
                    {
                        _instance = GetInstance();
                    }
                }
                return _instance;
            }
            set
            {
                _instance = value;
                if (value != null)
                {
                    InnerIdentifier = value.Id;
                }
                else
                {
                    _identifier = default(DirtyTrackable<TIdentifier>);
                }
            }
        }
        private TObject _instance;

        public TIdentifier Identifier
        {
            get
            {
                return InnerIdentifier;
            }
            set
            {
                InnerIdentifier = value;
                if (_instance != null && !_instance.Id.Equals(value))
                {
                    _instance = default(TObject);
                }
            }
        }

        private TIdentifier InnerIdentifier
        {
            get
            {
                if (_instance != null && _instance.Id.Equals(_identifier.Value)) {
                    _identifier.Value = _instance.Id;   
                }
                return _identifier.Value;
            }
            set
            {
                if (_identifier.HasValueSet) {
                    _identifier.Value = value;
                } else {
                    _identifier = new DirtyTrackable<TIdentifier>(value);
                }
            }
        }
        private DirtyTrackable<TIdentifier> _identifier;

        public bool IsDirty
        {
            get
            {
                return _identifier.IsDirty;
            }
        }
    }
}
